/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package idea.sfo.viewer.graphics;


import designersfo.Cons.CellInfo.CellFormat;
import designersfo.Cons.CellInfo.CustomContentCellData;
import idea.model.dbmanager.SfoDbManager;
import idea.sfo.viewer.Canvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Point2D;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.NoSuchElementException;
import org.openide.util.Exceptions;


/**
 *
 * @author renke002
 */
public class TextContent extends ContentLayout
{

    /*
     ************************STATIC-VARIABLES***********************************
     */
    /**
     * Utility variable to define an empty (as in zero length, not null) string.
     */
    private final static String EMPTY_STRING = "";
    /**
     *
     */
    private final static int LIST_TAG_BEGIN = 0;
    /**
     *
     */
    private final static int LIST_TAG_END = 1;
    // STRING TOKENS FROM EMR_TAG_PAIR
    /**
     * String token defining the beginning of a BOLD section.
     */
    public final static String TAG_BOLD_BEGIN = "<B>";
    /**
     * String token defining the end of a BOLD section.
     */
    public final static String TAG_BOLD_END = "</B>";
    /**
     * String token defining the beginning of a FONT section.
     */
    public final static String TAG_FONT_BEGIN = "<FONT ";
    /**
     * String token defining the end of a FONT section.
     */
    public final static String TAG_FONT_END = "</FONT>";
    /**
     * String token defining the location of a LINE BREAK (or new line).
     */
    public final static String TAG_LINE_BREAK = "<BR/>";
    /**
     * String token defining the beginning of an ITALIC section.
     */
    public final static String TAG_ITALIC_BEGIN = "<I>";
    /**
     * String token defining the end of an ITALIC section.
     */
    public final static String TAG_ITALIC_END = "</I>";
    /**
     * String token defining the beginning of a new PARAGRAPH.
     */
    public final static String TAG_PARAGRAPH = "<P/>";
    /**
     * String token defining the beginning of an SUB-SCRIPT section.
     */
    public final static String TAG_SUB_BEGIN = "<SUB>";
    /**
     * String token defining the end of an SUB-SCRIPT section.
     */
    public final static String TAG_SUB_END = "</SUB>";
    /**
     * String token defining the beginning of an SUPER-SCRIPT section.
     */
    public final static String TAG_SUP_BEGIN = "<SUP>";
    /**
     * String token defining the end of an SUPER-SCRIPT section.
     */
    public final static String TAG_SUP_END = "</SUP>";
    /**
     * String token defining the beginning of an UNDERLINED section.
     */
    public final static String TAG_UNDERLINE_BEGIN = "<U>";
    /**
     * String token defining the end of an UNDERLINED section.
     */
    public final static String TAG_UNDERLINE_END = "</U>";
    /**
     * List of tags that define text formatting: line brakes, paragraphs, etc.
     */
    public final static String[] TAGS_FORMAT =
    {
        TAG_LINE_BREAK ,
        TAG_PARAGRAPH
    };
    /**
     * List of tags defining the location of graphical elements: images, shapes,
     * non-text objects.
     */
    public final static String[] TAGS_GRAPHICS =
    {
    };
    /**
     * List of tag PAIRS (begin and end, open and close elements) defining how
     * to style the string contained between them.
     */
    public final static String[][] TAGS_STYLING =
    {
        {
            TAG_BOLD_BEGIN , TAG_BOLD_END
        } ,
        {
            TAG_ITALIC_BEGIN , TAG_ITALIC_END
        } ,
        {
            TAG_SUB_BEGIN , TAG_SUB_END
        } ,
        {
            TAG_SUP_BEGIN , TAG_SUP_END
        } ,
        {
            TAG_UNDERLINE_BEGIN , TAG_UNDERLINE_END
        }
    };
    /*
     *
     ************************INSTANCE-VARIABLES*********************************
     *
     */
    /**
     * Horizontal and Vertical alignment of the content.
     */
//    private ContentAlignment contAlign;
    /**
     * Styled text.
     */
    private AttributedCharacterIterator charIter;
    /**
     *
     */
    private Rectangle container;
    /**
     * True when the text is ready to be drawn onto the canvas, false when the
     * text needs to be processed.
     * <p/>
     * In order to accurately draw the text onto the canvas we need to know how
     * much space the font takes in a given representation. The only way to get
     * this is through FontMetrics, provided by the Graphics2D object.
     * <p/>
     * Since processing the text can slow down the application and the function
     * is (un-necessarily) repeated each time the draw( Graphics2D ) method is
     * called, we'll process the text once and then store it.
     * <p/>
     * The isDrawable flag will be set to true when the text has been processed
     * and false otherwise.
     */
    private boolean isDrawable;
    /**
     * String to decorate. Must retain the tags contained within it.
     */
    private String taggedText;
    /**
     *
     */
    private ArrayList<TextPrinter> styledText;
    /**
     *
     */
    private Color textColor;
    /**
     *
     */
    private HashMap<TextAttribute , Object> textAttribsMap;


    /**
     * Utility variable to define an empty string. This is not a NULL string,
     * just one of zero length, with no content.
     */
    /**
     * Create a new Text Styler object with the java.lang.String passed in as
     * parameter.
     * <p/>
     * @param str
     * @deprecated
     */
    @Deprecated
    public TextContent( String str )
    {
//        super( null );
        this.taggedText = str;
        this.charIter = TextContent.buildCharIter( taggedText , null );
    }


    /**
     *
     * @param content
     * @param container
     * @param format
     * @deprecated
     */
    @Deprecated
    public TextContent(
            String content ,
            Rectangle container ,
            CellFormat format )
    {
//        super( null );
        this.taggedText = content;
        this.charIter = TextContent.buildCharIter( this.taggedText , null );
    }


    /**
     *
     * @param content_id
     * @param relative_index
     * @param container
     * @param format
     * @deprecated
     */
    @Deprecated
    public TextContent(
            String content_id ,
            String relative_index ,
            Rectangle container ,
            CellFormat format )
    {
        this(
                TextContent.findCellDataContent( content_id , relative_index ) ,
                container ,
                format );
    }


    /**
     * Create a new TextContent object.
     * <p/>
     * @param innerCont  Inner container for the parent cell, this rectangle
     *                   provides the usable area to display the text.
     * <p/>
     * @param cccData    CustomContentCellData: SFO Element containing values
     *                   for the text to display.
     * <p/>
     * @param cellFormat SFO Element containing text formatting information.
     */
    public TextContent(
            Rectangle innerCont ,
            String cccData ,
            CellFormat cellFormat )
    {
        /*
         * The container provides both the anchor point and the dimensions of
         * the rectangle we need to fit into.
         */
        this.container = innerCont;

        /*
         */
        this.isDrawable = false;

        /*
         * PROCESS CELL-DATA
         */
        this.taggedText = cccData;

        /*
         * PROCESS CELL-FORMAT
         */
        if ( cellFormat != null )
        {
            this.textAttribsMap = this.formatText( cellFormat );
        }
        else
        {
            this.textAttribsMap = new HashMap<>();
        }

        /*
         * Hashtable<TextAttribute, Object> map
         */
        this.charIter = TextContent.buildCharIter(
                this.taggedText , this.textAttribsMap );

    }


    /**
     * Create a new TextContent object.
     * <p/>
     * @param innerCont  Inner container for the parent cell, this rectangle
     *                   provides the usable area to display the text.
     * <p/>
     * @param cccData    CustomContentCellData: SFO Element containing values
     *                   for the text to display.
     * <p/>
     * @param cellFormat SFO Element containing text formatting information.
     * <p/>
     * @deprecated in favor of "TextContent( Rectangle innerCont , String
     * cccData , CellFormat cellFormat )". Making this change because
     * CustomContentCellData can contain more than just text, and this class
     * shouldn't be required to handle those different cases.
     */
    @Deprecated
    public TextContent(
            Rectangle innerCont ,
            CustomContentCellData cccData ,
            CellFormat cellFormat )
    {
        /*
         * The container provides both the anchor point and the dimensions of
         * the rectangle we need to fit into.
         */
        this.container = innerCont;

        /*
         */
        this.isDrawable = false;

        /*
         * PROCESS CELL-DATA
         */
        this.taggedText = TextContent.findCellDataContent( cccData );

        /*
         * PROCESS CELL-FORMAT
         */
        this.textAttribsMap = this.formatText( cellFormat );

        /*
         * Hashtable<TextAttribute, Object> map
         */
        this.charIter = TextContent.buildCharIter( this.taggedText ,
                this.textAttribsMap );

    }


    /**
     * Builds an AttributedCharacterIterator representing the styled string.
     * <p/>
     * @param text tokenized string to style.
     * <p/>
     * @param map  Text Attributes to values map that is applied to the entire
     *             string.
     * <p/>
     * @return an AttributedCharacterIterator representing the styled string.
     */
    public static AttributedCharacterIterator buildCharIter( String text , HashMap<TextAttribute , Object> map )
    {
        /*
         * This is the text that will be displayed on screen. It needs to be
         * stripped of all the tags and non-display characters.
         */
        String displayText = TextContent.removeAllTags( text );

        /*
         * An AttributedString holds text and related attribute information. It
         * may be used as the actual data storage in some cases where a text
         * reader wants to access attributed text through the
         * AttributedCharacterIterator interface.
         */
        AttributedString aStr = new AttributedString( displayText );

        //aStr.addAttribute( TextAttribute.SIZE , new Float( 11.0 ) );
        try
        {
            Font f = (Font) map.get( TextAttribute.FONT );

            Float fl = new Float( f.getSize() + 1 );
            aStr.addAttribute( TextAttribute.SIZE , fl );
            aStr.addAttribute( TextAttribute.FAMILY , f.getFontName() );
        }
        catch ( Exception e )
        {
            System.err.println( e );
        }

        /*
         * Number of opening tags in the 'TAGS_STYLING' array. There must be an
         * equal number of closing tags so we can just count the number of
         * opener. Since we need to loop through this array, we'll use a
         * variable instead of repeatedly calculating the array length.
         */
        int styleTags = TextContent.TAGS_STYLING.length;

        /*
         * singleTokenText is the original text stripped of all the tokens,
         * except the ones whose locations we're searching for.
         */
        String singleTokenText;

        String beginTag;
        String endTag;

        /*
         * Text styler holds attribute -> value mappings for a given tag.
         */
        TextStyler style;

        // for tag in list
        //      if tag in string
        //          style tag in string

        /*
         * For all the style tags, check if the opening tag is in the string and
         * if so style the text accordingly.
         */
        for ( int tag = 0; tag < styleTags; tag++ )
        {
            beginTag = TextContent.TAGS_STYLING[tag][TextContent.LIST_TAG_BEGIN];
            endTag = TextContent.TAGS_STYLING[tag][TextContent.LIST_TAG_END];

            if ( text.contains( beginTag ) )
            {
                // remove all tags except the one we're currently styling.
                singleTokenText = TextContent.removeTagsExcept( text , tag );

                /*
                 */
                style = TextStyler.mapTagToDecorator( beginTag );

                /*
                 * Get a list of all the tag locations in the text.
                 */
                Integer[][] tagLocations = TextContent.findTagLocations(
                        singleTokenText , beginTag , endTag );

                if ( tagLocations.length > 0 )
                {
                    /*
                     * Location of the begin/open and end/close tags in the
                     * string.
                     */
                    int beginIndex;
                    int endIndex;

                    for ( int loc = 0;
                            loc < tagLocations[TextContent.LIST_TAG_BEGIN].length;
                            loc++ )
                    {
                        beginIndex = tagLocations[TextContent.LIST_TAG_BEGIN][loc];
                        endIndex = tagLocations[TextContent.LIST_TAG_END][loc];

                        try
                        {
                            aStr.addAttribute( style.attribute , style.value ,
                                    beginIndex , endIndex );
                        }
                        catch ( IllegalArgumentException iae )
                        {
                            System.err.println( iae.toString() );
                        }
                    }
                }
            }
            // else? nothing to do.
        }
        //return aStr.getIterator();
        return aStr.getIterator( null );
    }


    /**
     *
     * @param g2d
     */
    @Override
    public void draw( Graphics2D g2d )
    {
        // turn ON font AntiAliasing (aka ClearType in Windows) to improve the
        // font readability.
//        g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING ,
//                RenderingHints.VALUE_ANTIALIAS_ON );
//        g2d.setRenderingHint( RenderingHints.KEY_RENDERING ,
//                RenderingHints.VALUE_RENDER_QUALITY );
        //
        g2d.setColor( this.textColor );

        if ( this.isDrawable == false )
        {
            // setup text drawing stuff.
            this.placeText( g2d );
        }
        // draw all the printable text objects.
        for ( TextPrinter tp : this.styledText )
        {
            tp.draw( g2d );
        }
    }


    /**
     *
     * @param cccData
     * @return string representation of the data
     */
    private static String findCellDataContent( CustomContentCellData cccData )
    {
        return findCellDataContent( cccData.getContentId() , cccData.
                getRelativeIndex() );
    }


    /**
     *
     * @param content_id
     * @param relative_index
     * @return
     */
    public static String findCellDataContent( String content_id , String relative_index )
    {
        /*
         * gids-designer:custom-content-cell-data
         *
         * When opening a question layout in the custom layout designer, the
         * first stage involves gathering up all of the bits and pieces of
         * content from the QUESTION_TITLE, CUSTOM_CONTENT_ITEMS and
         * CUSTOM_RESPONSES tables.
         *
         * Next, the content-id for each cell in the layout is matched to the
         * IDs gathered in the previous step. So, for example, if the content-id
         * matches a CUSTOM_CONTENT_ITEMS.ITEM_ID, and the relative-index is 4,
         * then we know that this cell contains a reference to the ITEM_WORDING
         * for that item, and so on.
         *
         * Labels, headers and reusable blocks work exactly the same way.
         * Pagemaster templates and regions work similarly, but the content is
         * looked up in different places.
         *
         *
         * content-id relative-index interpretation
         *
         * QUESTION_TITLE.QUEST_ID 0 reference to a header or reusable block in
         * its entirety 2 QUESTION_TITLE.TITLE 4 QUESTION_TITLE.QUEST_WORDING 7
         * question number (replaceable token, not explicitly present in
         * content)
         *
         * CUSTOM_CONTENT_ITEMS.ITEM_ID 3 CUSTOM_CONTENT_ITEMS.ITEM_NUMBER 4
         * CUSTOM_CONTENT_ITEMS.ITEM_WORDING 5
         * CUSTOM_CONTENT_ITEMS.HDR_COLUMN_REF 6
         * CUSTOM_CONTENT_ITEMS.HDR_KEYCODE
         *
         * CUSTOM_RESPONSES.RESPONSES_ID 1 reference to the response area itself
         * 4 CUSTOM_RESPONSES.RESPONSE_LABEL +
         * CUSTOM_RESPONSES.RESPONSE_INSTRUCTION (concatenated) 6
         * CUSTOM_RESPONSES.KEYCODE
         *
         * REGION_DEFINITIONS.REGION_DEF_ID 8 reference to computed page/face
         * barcode (ACS/Decennial only) 9 reference to computed page/face page
         * number (ACS/Decennial only)
         *
         */
        // get DB connection        
        // find stuff.
        if ( Integer.parseInt( relative_index ) == 0 )
        {
            //          QUESTION_TITLE.QUEST_ID
            // 0 reference to a header or reusable block in its entirety 
            SfoDbManager.findHeaders( content_id );
        }
        String content = SfoDbManager.findCellDataContent( content_id ,
                relative_index );
        return content;
    }


    /**
     *
     * @param tag
     * @return
     */
    private int findTagID( String tag )
    {
        for ( int i = 0; i < TAGS_STYLING[0].length; i++ )
        {
            if ( tag.contains( TAGS_STYLING[0][i] ) || tag.contains(
                    TAGS_STYLING[0][i] ) )
            {
                return i;
            }
        }
        return -1;
    }


    /**
     * Find the locations of the begin and end tags in the given string.
     * <p/>
     * @param source
     * @param beginToken
     * @param endToken
     * @return
     */
    public static Integer[][] findTagLocations( String source , String beginToken , String endToken )
    {
        ArrayList<Integer> begTokenLocations = new ArrayList<>();
        ArrayList<Integer> endTokenLocations = new ArrayList<>();

        int location;

        while ( source.contains( beginToken ) && source.contains( endToken ) )
        {
            location = source.indexOf( beginToken );
            begTokenLocations.add( location );
            // crop the source string to remove the section with the tokens
            // that have already been parsed.
            source = source.replaceFirst( beginToken , "" );

            location = source.indexOf( endToken );
            endTokenLocations.add( location );
            // crop the source string to remove the section with the tokens
            // that have already been parsed.
            source = source.replaceFirst( endToken , "" );
        }

        Integer[][] locations = new Integer[ 2 ][ begTokenLocations.size() ];

        locations[TextContent.LIST_TAG_BEGIN] = begTokenLocations.toArray(
                new Integer[ 0 ] );
        locations[TextContent.LIST_TAG_END] = endTokenLocations.toArray(
                new Integer[ endTokenLocations.size() ] );

        return locations;
    }


    /**
     * Parse the SFO CellFormat object for text related formatting data.
     * <p/>
     * @param cellFormat
     */
    private HashMap<TextAttribute , Object> formatText( CellFormat cellFormat )
    {
        try
        {
            this.textColor = ColorSpec.parseColorString( cellFormat.
                    getFontColor() );
        }
        catch ( IllegalArgumentException | NoSuchElementException e )
        {
            Exceptions.printStackTrace( e );

            // default to a black text.
            this.textColor = Color.BLACK;
        }

        // setup the font and its attributes
        Font textFont = TextStyler.buildFont( cellFormat );

        HashMap<TextAttribute , Object> map = new HashMap<>();

        map.put( TextAttribute.FONT , textFont );

        //
        map.put( TextAttribute.RUN_DIRECTION , cellFormat.getHorzAlignment() );
        //
        map.put( TextAttribute.POSTURE , cellFormat.getVertAlignment() );
//        map.put( TextAttribute.FAMILY , textFont.getFamily() );
//        map.put( TextAttribute.SIZE , textFont.getSize() );

        return map;
    }


    /**
     *
     * @return an AttributedCharacterIterator
     * @deprecated
     */
    @Deprecated
    public AttributedCharacterIterator getCharIter()
    {
        return this.charIter;
    }


    /**
     * Get the raw text (inclusive of formatting tags) contained in this object.
     * <p/>
     * @return the raw text (inclusive of tags) contained in this object.
     */
    @Override
    public String getLabel()
    {
        return this.taggedText;
    }


    /**
     * Get the text, purged of all the formatting tags.
     * <p/>
     * @return the raw text
     */
    public String getText()
    {
        return removeAllTags( this.taggedText );
    }


//    /**
//     *
//     * @param x
//     * @param y
//     */
//    @Override
//    public void moveTo( int x , int y )
//    {
//        this.container.x = x;
//        this.container.y = y;
//        this.isDrawable = false;
//    }
    /**
     * Draw the text provided by the character iterator onto the graphics canvas
     * at the location and within the size provided by the container.
     * <p/>
     * @param g2d
     */
    public void placeText( Graphics2D g2d )
    {
//        if ( this.horzAlign != null )
//        {
//        }
//        if ( this.vertAlign != null )
//        {
//        }
        String alignHorz = (String) this.textAttribsMap.get(
                TextAttribute.RUN_DIRECTION );

        String alignVert = (String) this.textAttribsMap.get(
                TextAttribute.POSTURE );
        //
        this.styledText = new ArrayList<>();


        /*
         * Since we need to take into consideration the case in which text can
         * be vertically aligned to the bottom of the container, we need to
         * first break the text along however many lines necessary, then
         * calculated the height of these lines to find the starting point.
         */
        HashMap<Point2D.Float , TextLayout> layoutMap = new HashMap<>();

        /*
         * A java.awt.font.LineBreakMeasurer class allows styled text to be
         * broken into lines (or segments) that fit within a particular visual
         * advance. This is useful for clients who wish to display a paragraph
         * of text that fits within a specific width, called the wrapping width.
         */
        LineBreakMeasurer lbMeasurer = new LineBreakMeasurer( this.charIter ,
                g2d.getFontRenderContext() );

        lbMeasurer.setPosition( this.charIter.getBeginIndex() );
        int charIterEnd = this.charIter.getEndIndex();

        // advance for the x-coord calculated based on text alignment.
        float alignX, alignY;
        //
        float totalHeight = 0;
        //
        float descent = 0;
        //
        TextLayout layout;
        /*
         * loop through the length of the character iterator until the entire
         * block has been split.
         */
        while ( lbMeasurer.getPosition() < charIterEnd )
        {
            /*
             * Get a Text Layout object which represents a block of text that
             * fits within the available length.
             */
            layout = lbMeasurer.nextLayout( this.container.width );

            /*
             * Calculate the X-coordinate based on horizontal alignment.
             *
             * 0 -> (width - advance) : left,center,right alignment.
             */
            alignX = this.container.x;
            if ( alignHorz != null && alignHorz.isEmpty() == false )
            {
                if ( alignHorz.equals( ContentLayout.HORIZONTAL_NAME_RIGHT ) )
                {
                    alignX += this.container.width - layout.getAdvance();
                }
                else if ( alignHorz.endsWith(
                        ContentLayout.HORIZONTAL_NAME_CENTER ) )
                {
                    alignX += ( this.container.width - layout.getAdvance() ) / 2;
                }
            }

            //
            totalHeight += layout.getAscent();

            /*
             */
            layoutMap.put( new Point2D.Float( alignX , totalHeight ) , layout );

            descent = layout.getDescent();
            //
            //totalHeight += ( layout.getDescent() + layout.getLeading() );
            totalHeight += ( layout.getLeading() );
        }

        /*
         * Calculate the X-coordinate based on horizontal alignment.
         *
         * 0 -> (width - advance) : left,center,right alignment.
         */
        alignY = this.container.y;

        if ( alignVert != null && alignVert.isEmpty() == false )
        {

            if ( alignVert.equals( ContentLayout.VERTICAL_NAME_BOTTOM ) )
            {
                alignY += ( this.container.height - totalHeight - descent );
            }
            else if ( alignVert.endsWith( ContentLayout.VERTICAL_NAME_CENTER ) )
            {
                alignY += ( this.container.height - totalHeight ) / 2;
            }
        }


        for ( Point2D.Float p : layoutMap.keySet() )
        {
            // all done, add the text 
            this.styledText.add( new TextPrinter( layoutMap.get( p ) , p.x ,
                    p.y + alignY ) );
        }

        /*
         * If everything has gone well, the text can now be drawn on the canvas
         * proved by the Graphics2D object in the draw() method. To signal this
         * set the 'isDrawable' flag to true.
         */
        this.isDrawable = true;
    }


    /**
     * Remove all formatting, styling, etc tags from a text string.
     * <p/>
     * @param text
     * @return
     */
    private static String removeAllTags( String text )
    {
        // formatting tags
        for ( int i = 0; i < TextContent.TAGS_FORMAT.length; i++ )
        {
            text = text.replaceAll( TextContent.TAGS_FORMAT[i] ,
                    TextContent.EMPTY_STRING );
        }
        // graphics tags
        for ( int j = 0; j < TextContent.TAGS_GRAPHICS.length; j++ )
        {
            text = text.replaceAll( TextContent.TAGS_GRAPHICS[j] ,
                    TextContent.EMPTY_STRING );
        }
        // styling tags
        for ( int k = 0; k < TextContent.TAGS_STYLING.length; k++ )
        {
            text = text.replaceAll(
                    TextContent.TAGS_STYLING[k][TextContent.LIST_TAG_BEGIN] ,
                    TextContent.EMPTY_STRING );
            text = text.replaceAll(
                    TextContent.TAGS_STYLING[k][TextContent.LIST_TAG_END] ,
                    TextContent.EMPTY_STRING );
        }
        return text;
    }


    /**
     * Remove all the styling tags from a string, except for the one who's id is
     * provided.
     * <p/>
     * @param text
     * @param skipTagNumber
     * @return
     */
    private static String removeTagsExcept( String text , int skipTagNumber )
    {
        for ( int k = 0; k < TextContent.TAGS_STYLING.length; k++ )
        {
            if ( k != skipTagNumber )
            {
                text = text.replaceAll(
                        TextContent.TAGS_STYLING[k][TextContent.LIST_TAG_BEGIN] ,
                        TextContent.EMPTY_STRING );
                text = text.replaceAll(
                        TextContent.TAGS_STYLING[k][TextContent.LIST_TAG_END] ,
                        TextContent.EMPTY_STRING );
            }
        }
        return text;
    }


    /**
     *
     * @param label
     */
    @Override
    public void setLabel( String label )
    {
        this.setText( label );
    }


    /**
     * Set this object's text to the parameter.
     * <p/>
     * @param text
     */
    public void setText( String text )
    {
        this.taggedText = text;
        //
        this.charIter = TextContent.buildCharIter( text , this.textAttribsMap );
        //
        this.isDrawable = false;
    }


    /**
     *
     */
    private class TextPrinter implements Drawable
    {

        TextLayout layout;
        float drawX;
        float drawY;


        /**
         *
         * @param tl
         * @param x
         * @param y
         */
        public TextPrinter( TextLayout tl , float x , float y )
        {
            this.layout = tl;
            this.drawX = x;
            this.drawY = y;
        }


        /**
         * Draws the text on the canvas provided by the Graphics2D parameter.
         * <p/>
         * @param g2d canvas to draw the text onto.
         */
        @Override
        public void draw( Graphics2D g2d )
        {
            System.out.println( " x: " + this.drawX + " y: " + this.drawY );
            this.layout.draw( g2d , this.drawX , this.drawY );
        }


//        /**
//         * 
//         * @param x
//         * @param y 
//         */
//        @Override
//        public void moveTo( int x , int y )
//        {
//            this.drawX = x;
//            this.drawY = y;
//        }
        /**
         * Sets the x-coordinate to the parameter.
         * <p/>
         * @param x new x-coordinate.
         */
        public void setX( float x )
        {
            this.drawX = x;
        }


        /**
         * Sets the y-coordinate to the parameter.
         * <p/>
         * @param y new y-coordinate.
         */
        public void setY( float y )
        {
            this.drawY = y;
        }

    }


    /**
     * The TextModifier class handles java.awt.font.TextAttribute objects used
     * for text rendering.
     * <p/>
     * These object are used for the java.text.AttributedString.add( attribute ,
     * value, beginIndex, endIndex ) method to decorate a text string.
     */
    private static class TextStyler
    {
        /*
         * java.text.AttributedString.addAttribute(
         * AttributedCharacterIterator.Attribute attribute, Object value, int
         * beginIndex, int endIndex)
         */

        /*
         * Font Style Enumeration as defined on the wiki:
         * https://censuswiki.fenestra.com/bin/view/Main/FontStyleEnum
         *
         * The font style enumeration comprises three keyword values, each of
         * which represents a style for rendering text: normal - render the text
         * normally. italic - render the text in italics. oblique - render the
         * text obliquely.
         *
         * NOTES: 1.The keywords are case-sensitive. 2.Stricly speaking, italic
         * and oblique represent different styles; in practice, however, they
         * are nearly always rendered identically (the precise rendering rules
         * are font-dependent).
         */
        /**
         * Render the text in <i>ITALICS</i>.
         */
        public final static String FONT_STYLE_ITALIC = "italic";
        /**
         * Render the text normally.
         */
        public final static String FONT_STYLE_NORMAL = "normal";
        /**
         * Render the text with an <i>OBLIQUE</i> font.
         * <p/>
         * In most cases oblique text renders the same as italic, although they
         * are technically different.
         */
        public final static String FONT_STYLE_OBLIQUE = "oblique";
        /**
         * Modifier Type, a java.awt.font.TextAttribute.
         * <p/>
         * The attribute defines the decoration that is applied to a text string
         */
        private final TextAttribute attribute;
        /**
         * AttributedString
         */
        private final Object value;


        /**
         * Create a new TextStyler object with the provided parameters.
         * <p/>
         * @param modifier
         * @param value
         */
        private TextStyler( TextAttribute modifier , Object value )
        {
            this.attribute = modifier;
            this.value = value;
        }


        /**
         *
         * @param cellFormat Cell-Format element containing the font
         *                   specification.
         * <p/>
         * @return java.awt.Font matching the values defined in the Cell-Format
         *         element.
         */
        public static Font buildFont( CellFormat cellFormat )
        {
            /*
             * From
             * https://censuswiki.fenestra.com/bin/view/Main/FontDecorationEnum
             *
             * Font decoration enumeration
             *
             * The font decoration enumeration comprises four keyword values,
             * each of which represents a style for decorating text: *
             *
             * no-underline - render the text normally; no-strikethrough -
             * render the text normally; strikethrough - render strikethrough
             * text; underline - render underlined text.
             *
             * NOTES 1.The keywords are case-sensitive. 2.no-underline and
             * no-strikethrough have the same effect; they are both included for
             * compatibility with the XSL Formatting Objects (XSL-FO)
             * specification.
             *
             * Currently un-used.
             */
            //String fontDecoration = cellFormat.getFontDecoration();

            // 
            String fontFamily = cellFormat.getFontFamily();

            //
            String fontAngle = cellFormat.getFontRotationAngle();


            //
            int fontSize = Canvas.parseFUSize( cellFormat.getFontSize() ); //;

            // normal, italic, or oblique
            int fontStyle = TextStyler.
                    mapStyleToFont( cellFormat.getFontStyle() );

            // more than 400 is bold, less than 400 is light
            String fontWeight = cellFormat.getFontWeight();

            /*
             * java.awt.Font(String name, int style, int size) Creates a new
             * Font from the specified name, style and point size.
             */
            Font f = new Font( "Univers" , fontStyle , fontSize );

            return f;
        }


        /**
         * Given an SFO defined font style enumeration value, this function will
         * provide the corresponding java.awt.Font style.
         * <p/>
         * @param fontStyleEnum font style enumeration to use.
         * <p/>
         * @return java.awt.Font style corresponding to the font style
         *         enumeration parameter.
         * <p/>
         * @throws IllegalArgumentException if the parameter does not match one
         *                                  of the supported font style
         *                                  enumerations.
         */
        public static int mapStyleToFont( String fontStyleEnum )
                throws IllegalArgumentException
        {
            switch ( fontStyleEnum )
            {
                case TextStyler.FONT_STYLE_ITALIC:
                    return Font.ITALIC;
                case TextStyler.FONT_STYLE_NORMAL:
                    return Font.PLAIN;
                case TextStyler.FONT_STYLE_OBLIQUE:
                    return Font.ITALIC;
                default:
                    throw new IllegalArgumentException(
                            "No matching text attribute." );
            }
        }


        /**
         * Maps a tag from the EMR_TAG_PAIR table to the corresponding
         * TextAttribute modifier and value for text rendering.
         * <p/>
         * @param emrTag - a tag from the EMR_TAG_PAIR table.
         * @return - a TextModifier object with the corresponding text attribute
         *         modification and value for the emr tag parameter.
         * @throws IllegalArgumentException - if there is no matching text
         *                                  attribute for the emr tag parameter.
         */
        public static TextStyler mapTagToDecorator( String emrTag ) throws
                IllegalArgumentException
        {
            switch ( emrTag.toUpperCase() )
            {
                case TextContent.TAG_BOLD_BEGIN:
                case TextContent.TAG_BOLD_END:
                    return new TextStyler( TextAttribute.WEIGHT ,
                            TextAttribute.WEIGHT_BOLD );
                case TextContent.TAG_ITALIC_BEGIN:
                case TextContent.TAG_ITALIC_END:
                    return new TextStyler( TextAttribute.POSTURE ,
                            TextAttribute.POSTURE_OBLIQUE );
                case TextContent.TAG_SUB_BEGIN:
                case TextContent.TAG_SUB_END:
                    return new TextStyler( TextAttribute.SUPERSCRIPT ,
                            TextAttribute.SUPERSCRIPT_SUB );
                case TextContent.TAG_SUP_BEGIN:
                case TextContent.TAG_SUP_END:
                    return new TextStyler( TextAttribute.SUPERSCRIPT ,
                            TextAttribute.SUPERSCRIPT_SUPER );
                case TextContent.TAG_UNDERLINE_BEGIN:
                case TextContent.TAG_UNDERLINE_END:
                    return new TextStyler( TextAttribute.UNDERLINE ,
                            TextAttribute.UNDERLINE_ON );
                default:
                    throw new IllegalArgumentException(
                            "No matching text attribute." );

            }
        }

    }

}
